package com.glodon.paas.document.service.impl;

import static com.glodon.paas.document.constant.DocumentConstant.FILE_STATUS_NORMAL;
import static com.glodon.paas.document.constant.DocumentConstant.FILE_TYPE_FILE;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.glodon.paas.consts.StringConst;
import com.glodon.paas.document.api.bean.File;
import com.glodon.paas.document.api.bean.Revision;
import com.glodon.paas.document.biz.FileBiz;
import com.glodon.paas.document.biz.ProjectBiz;
import com.glodon.paas.document.biz.ProjectResourceBiz;
import com.glodon.paas.document.biz.RevisionBiz;
import com.glodon.paas.document.biz.ShareBiz;
import com.glodon.paas.document.constant.DocumentConstant;
import com.glodon.paas.document.dao.domain.FileInfoEntity;
import com.glodon.paas.document.dao.domain.MultiPartUploadHandleEntity;
import com.glodon.paas.document.dao.domain.PartEntity;
import com.glodon.paas.document.dao.domain.ProjectInfoEntity;
import com.glodon.paas.document.dao.domain.PublicTokenEntity;
import com.glodon.paas.document.dao.domain.RevisionEntity;
import com.glodon.paas.document.service.FileService;
import com.glodon.paas.document.service.util.DocumentConfig;
import com.glodon.paas.document.service.util.DocumentPrivilege;
import com.glodon.paas.document.service.util.OrderUtilsFile;
import com.glodon.paas.document.service.util.StorageKeyNamingPolicy;
import com.glodon.paas.document.service.util.WebService;
import com.glodon.paas.document.service.util.WrapInputstream;
import com.glodon.paas.dto.DataTansitionObject;
import com.glodon.paas.exception.PaasServerException;
import com.glodon.paas.exception.PaasServerExceptionFactory;
import com.glodon.paas.exception.StorageException;
import com.glodon.paas.exception.i18n.ErrorCode;
import com.glodon.paas.storage.BulkStorage;
import com.glodon.paas.storage.InitResult;
import com.glodon.paas.storage.ReadResult;
import com.glodon.paas.storage.WriteResult;
import com.glodon.paas.util.DateUtil;
import com.glodon.paas.util.FileUtil;
import com.glodon.paas.util.HttpUtils;
import com.glodon.paas.util.SimpleBeanConverter;
import com.glodon.paas.util.UUIDUtil;

@Service("fileService")
public class FileServiceImpl implements FileService {

    private static final Logger LOGGER = LoggerFactory.getLogger(FileServiceImpl.class);

    @Autowired
    private FileBiz             fileBiz;
    
    @Autowired
    private ProjectBiz          projectBiz;

    @Autowired
    private RevisionBiz         revisionBiz;

    @Autowired
    private ShareBiz            shareBiz;
    
    @Autowired
    private ProjectResourceBiz resourceBiz;

    @Autowired
    private BulkStorage         bulkStorage;
    
    @Autowired
    private WebService webService;

    public BulkStorage getBulkStorage() {
        return bulkStorage;
    }

    @Autowired
    private DocumentPrivilege privilege;

    @Override
    public File getFile(String userId, String fileId, boolean cascade) {
        privilege.checkReadPrivilege(userId, fileId, true, true);
        File file = FileInfoEntity.getDto(fileBiz.getNormalFile(fileId), cascade);
        resetPrivilege(userId, file);
        return file;
    }
    
    
    /** 只有进入权限的项目文件夹的 count 和folderCount 值需要检查子文件的权限
     * @param userId
     * @param file
     */
    private void resetPrivilege(String userId, File file){
        if(file != null && file.getProjectId() != null){
            Set<String> pPV = resourceBiz.getFilePrivileges(userId, file.getId(), true);
            if(pPV.size() == 1 && pPV.contains(DocumentConstant.FILE_ENTER)){
                Set<String> iPV = selfPV2InheritPV(pPV);
                int count=0;
                int folderCount = 0;
                List<FileInfoEntity> files = fileBiz.getFiles(file.getId(), null, new Integer[] { DocumentConstant.FILE_STATUS_NORMAL }, null);
                for(FileInfoEntity f : files){
                    Set<String> sPV = resourceBiz.getFilePrivileges(userId, f.getId(), false);
                    sPV.addAll(iPV);
                    if(sPV.size()>0){
                        count ++;
                        if(f.isFolder()){
                            folderCount++;
                        }
                    }
                }
                file.setCount(count);
                file.setFolderCount(folderCount);
            }
            file.getAttributes().put(DocumentConstant.FILE_PRIVILEGES, pPV.toArray());
        }
    }

    @Override
    public List<File> getFiles(String userId, String fileId, String orderBy, String orderType, boolean cascade) {
        List<File> files;
        FileInfoEntity parentFile;
        //个人文档 根目录
        if(userId.equals(fileId)){
            parentFile = null;
        }
        else{
            parentFile = fileBiz.getFile(fileId);
        }
        //项目文件
        if(parentFile != null && parentFile.getProjectId() != null){
            files = this.getProjectFiles(userId, fileId, orderBy, orderType, cascade);
        }
        //个人文件
        else{
            privilege.checkOwnerSharer(userId, fileId, true, true);
            files = FileInfoEntity.getDtos(fileBiz.getFiles(fileId, null, new Integer[] { DocumentConstant.FILE_STATUS_NORMAL }, null), cascade);
        }
        if (StringUtils.isNotBlank(orderBy)) {
            OrderUtilsFile.order(files, orderBy, orderType);
        }
        return files;
    }
    
    private List<File> getProjectFiles(String userId, String fileId, String orderBy, String orderType, boolean cascade) {
        Set<String> pPV = resourceBiz.getFilePrivileges(userId, fileId, true);
        List<File> result = new ArrayList<File>();
        // 进入权限级别最低，有任何权限 都可进入文件夹内部
        if (pPV.size() > 0) {
            List<File> files = FileInfoEntity.getDtos(fileBiz.getFiles(fileId, null, new Integer[] { DocumentConstant.FILE_STATUS_NORMAL }, null), cascade);
            if (files.size() > 0) {
                // 将 权限 变成 继承权限
                Set<String> iPV = selfPV2InheritPV(pPV);
                for (File file : files) {
                    // 如果是文件夹，需要检查是否有进入权限
                    if (file.isFolder()) {
                        Set<String> sPV = resourceBiz.getFilePrivileges(userId, file.getId(), false);
                        sPV.addAll(iPV);
                        if (sPV.size() > 0) {
                            // 如何只有进入权限，需要从新 设置 count ， folderCount
                            if(sPV.size()==1 && sPV.contains(DocumentConstant.FILE_ENTER)){
                                resetPrivilege(userId, file);
                            }
                            // 添加该文件夹的继承权限
                            file.getAttributes().put(DocumentConstant.FILE_PRIVILEGES, sPV.toArray());
                            result.add(file);
                        }
                    } else if(iPV.contains(DocumentConstant.FILE_INHERIT_READ)){
                        file.getAttributes().put(DocumentConstant.FILE_PRIVILEGES, iPV.toArray());
                        result.add(file);
                    }
                }
            }
            return result;
        } else {
            throw PaasServerExceptionFactory.privilegeException("User[" + userId + "] has no privilege to get reource["
                                                                + fileId + "]", null);
        }
    }

    @Override
    public List<Revision> getRevisions(String userId, String fileId) {
        privilege.checkReadPrivilege(userId, fileId, true, true);
        return RevisionEntity.getDtos(revisionBiz.getRevisions(fileId));
    }

    @Override
    public long getUsedSpace(String userId) {
        return fileBiz.getUsedSpace(userId);
    }

    @Override
    public String generateFolderName(String userId, String parentId) {
        privilege.checkAnyPrivilege(userId, parentId, true, true);
        // 新建文件夹
        String folderName = "\u65b0\u5efa\u6587\u4ef6\u5939";

        if (fileBiz.isFolderExist(parentId, folderName)) {
            for (int i = 1; i < Integer.MAX_VALUE; i++) {
                if (!fileBiz.isFolderExist(parentId, folderName + '(' + i + ')')) {
                    return folderName + '(' + i + ')';
                }
            }
        }
        return folderName;
    }

    private String buildPath(String ownerId, String creator, String parentId, String path,
                             List<FileInfoEntity> firstFolders) {
        FileInfoEntity folder = null;
        FileInfoEntity firstFolder = null;
        boolean queryFolder = true;
        String[] names = path.split(StringConst.SEPARATOR_SLASH);
        for (int i = 0; i < names.length; i++) {
            String name = names[i];
            if (StringUtils.isBlank(name)) {
                continue;
            }
            if (queryFolder) {
                folder = fileBiz.getFile(parentId, name, new Integer[] { DocumentConstant.FILE_STATUS_NORMAL },
                                         new Integer[] { DocumentConstant.FILE_TYPE_FOLDER });
                if (folder == null) {
                    firstFolder = this.newFolder(ownerId, creator, parentId, name);
                    parentId = firstFolder.getId();
                    queryFolder = false;
                } else {
                    parentId = folder.getId();
                }
            } else {
                parentId = this.newFolder(ownerId, creator, parentId, name).getId();
            }
        }
        if (firstFolders != null && firstFolder != null) {
            firstFolders.add(firstFolder);
        }
        return parentId;
    }

    private FileInfoEntity newFolder(String ownerId, String creatorId, String parentId, String folderName) {
        if (!FileUtil.isValidatorFileName(folderName)) {
            throw PaasServerExceptionFactory.validateException("folder name is incorrect!", null).setErrorCode(ErrorCode.INVALIDATE_FILE_NAME);
        }
        if (fileBiz.isFolderExist(parentId, folderName)) {
            throw PaasServerExceptionFactory.existException("the same folder existed!", null).setErrorCode(ErrorCode.EXIST_SAME_FOLDER);
        }

        FileInfoEntity folder = new FileInfoEntity();
        folder.setId(UUIDUtil.getUUIDStr());
        folder.setName(folderName);
        folder.setExtension("");
        folder.setType(DocumentConstant.FILE_TYPE_FOLDER);
        Date now = DateUtil.getNow();
        folder.setCreateTime(now);
        folder.setUpdateTime(now);
        folder.setOwnerId(ownerId);
        folder.setParentId(parentId);
        folder.setDeletedStatus(DocumentConstant.FILE_STATUS_NORMAL);
        folder.setCreator(creatorId);
        folder.setUpdator(creatorId);
        fileBiz.insertFile(folder);
        return folder;
    }

    @Override
    public File createFolder(String userId, String parentId, String path) {
        privilege.checkWritePrivilege(userId, parentId, true, true);
        String folderName = path;
        String ownerId;
        //个人文档, parentId == userId时，为 根目录。此时owner 为当前操作者
        if(userId.equals(parentId)){
            ownerId = userId;
        }
        // owner 应该继承下去
        else{
            ownerId = fileBiz.getFile(parentId).getOwnerId();
        }
        if (path.indexOf(StringConst.SEPARATOR_SLASH) > 0) {
            folderName = path.substring(path.lastIndexOf(StringConst.SEPARATOR_SLASH) + 1, path.length());
            path = path.substring(0, path.lastIndexOf(StringConst.SEPARATOR_SLASH));
            parentId = buildPath(ownerId, userId, parentId, path, null);
        }
        File folder = FileInfoEntity.getDto(newFolder(ownerId, userId, parentId, folderName), false);
        resetPrivilege(userId, folder);
        return folder;
    }

    @Override
    public File rename(String updatorId, String fileId, String fileName) {
        if (!FileUtil.isValidatorFileName(fileName)) {
            throw PaasServerExceptionFactory.validateException("file name is incorrect!", null);
        }
        privilege.checkWritePrivilege(updatorId, fileId, true, true);
        FileInfoEntity file = fileBiz.getNormalFile(fileId);
        if (file.isFolder()) {
            if (fileBiz.isFolderExist(file.getParentId(), fileName)) {
                throw PaasServerExceptionFactory.existException("the same folder existed!", null).setErrorCode(ErrorCode.EXIST_SAME_FOLDER);
            }
        } else if (fileBiz.isDocExist(file.getParentId(), fileName + "." + file.getExtension())) {
            {
                throw PaasServerExceptionFactory.existException("the same file existed!", null).setErrorCode(ErrorCode.EXIST_SAME_FILE);
            }
        }
        fileBiz.updateFileName(fileId, fileName, updatorId);
        WebService.notifySearch(FileInfoEntity.getDto(file, false), "rename");
        file.setName(fileName);
        file.setUpdator(updatorId);
        
        File ret = FileInfoEntity.getDto(file, false);
        resetPrivilege(updatorId, ret);
        return ret;
    }

    @Override
    public File remove(String userId, String fileId) {
        FileInfoEntity file = fileBiz.getFile(fileId);
        if (file != null && !file.isDeleted()) {
            privilege.checkRemovePrivilege(userId, fileId, true, true);
            fileBiz.removeFile(file.getId());
        }

        File ret = FileInfoEntity.getDto(file, false);
        resetPrivilege(userId, ret);
        return ret;
    }

    @Override
    public void removeUploadingRevision(String userId, String fileId) {
        privilege.checkWritePrivilege(userId, fileId, true, true);
        revisionBiz.deleteRevision(revisionBiz.getUploadingRevision(fileId));
    }

    private FileInfoEntity initFile(String ownerId, String creatorId, String parentId, String path, long fileSize,
                                    List<FileInfoEntity> createdFolders) {
        String fileName = path;
        if (path.indexOf(StringConst.SEPARATOR_SLASH) >= 0) {
            fileName = path.substring(path.lastIndexOf(StringConst.SEPARATOR_SLASH) + 1, path.length());
            path = path.substring(0, path.lastIndexOf(StringConst.SEPARATOR_SLASH));
            parentId = buildPath(ownerId, creatorId, parentId, path, createdFolders);
        }

        if (!FileUtil.isValidatorFileName(fileName)) {
            PaasServerExceptionFactory.validateException("file name is incorrect!", null);
        }
        FileInfoEntity file = fileBiz.getFile(parentId, fileName, new Integer[] { FILE_STATUS_NORMAL },
                                              new Integer[] { FILE_TYPE_FILE });
        if (file == null) {
            file = new FileInfoEntity();
            file.setId(UUIDUtil.getUUIDStr());
            file.setName(FilenameUtils.getBaseName(fileName));
            file.setExtension(FilenameUtils.getExtension(fileName));
            file.setType(DocumentConstant.FILE_TYPE_FILE);
            file.setCreateTime(DateUtil.getNow());
            file.setParentId(parentId);
            file.setOwnerId(ownerId);
            file.setCreator(creatorId);
            file.setUpdator(creatorId);
            file.setCount(0);
            file.setDeletedStatus(DocumentConstant.FILE_STATUS_NORMAL);
            file.setSize(0L);
        } else {
            file.setUpdator(creatorId);
        }
        return file;
    }

    @Override
    public File uploadFile(String creatorId, String parentId, String path, long position, long length, InputStream data) {
        if (length < 0) {
            throw PaasServerExceptionFactory.validateException("file size[" + length + "] is incorrect.", null);
        }
        privilege.checkWritePrivilege(creatorId, parentId, true, true);
        String ownerId;
        FileInfoEntity parentFolder = null;
        //个人文档,parentId 为空 或 parentId == userId时，为 根目录。此时owner 为当前操作者
        if(creatorId.equals(parentId)){
            ownerId = creatorId;
        }
        // owner 应该继承下去
        else{
            parentFolder = fileBiz.getFile(parentId);
            ownerId = parentFolder.getOwnerId();
        }
        // 暂时不考虑项目文件的使用空间限制
        if(parentFolder != null && parentFolder.getProjectId() != null){
            
        }
        else if(fileBiz.getUsedSpace(ownerId) + length > DocumentConfig.DEFAULT_USE_SPACE) {
            throw PaasServerExceptionFactory.documentException("no enough space to save file:" + path, null).setErrorCode(ErrorCode.NO_ENOUGH_SPACE);
        }
        try {
            FileInfoEntity file = null;
            if (bulkStorage.isMultiPartUploadSupported() && length > BulkStorage.BLOCK_SIZE) {
                WrapInputstream wis = new WrapInputstream(data);
                file = uploadChunk(ownerId, creatorId, parentId, path, position, length, wis);
            } else {
                file = uploadCommon(ownerId, creatorId, parentId, path, length, data);
            }
            File ret = FileInfoEntity.getDto(fileBiz.getNormalFile(file.getId()), false);
            WebService.notifySearch(ret, "upload");
            resetPrivilege(creatorId, ret);
            
            return ret;
        } finally {
            if (data != null) {
                try {
                    data.close();
                } catch (IOException ignore) {
                }
            }
        }
    }

    private FileInfoEntity uploadChunk(String ownerId, String creatorId, String parentId, String path, long position,
                                       long fileSize, WrapInputstream wis) {
        WriteResult result = null;
        InitResult handler = null;
        List<FileInfoEntity> folders = new ArrayList<FileInfoEntity>();
        // init File
        FileInfoEntity file = initFile(ownerId, creatorId, parentId, path, fileSize, folders);
        long uploadedBytes = this.getUploadedBytes(file.getId());

        // init revision
        RevisionEntity revision = revisionBiz.getUploadingRevision(file.getId());
        // 如何大小发生变化，删除旧版本，创建新版本
        if (revision != null) {
            if (revision.getFileSize() != fileSize || (position == 0 && uploadedBytes != position)) {
                revisionBiz.deleteRevision(revision);
                revision = null;
            } else if (position < uploadedBytes) {
                try {
                    // if skip() has exception, use the code to skip gap bytes.
                    // long gap = uploadedBytes - position;
                    // long index = 0L;
                    // byte buffer[] = new byte[4096];
                    // while(true)
                    // {
                    // if(index>=gap){
                    // break;
                    // }
                    // if(index+buffer.length>gap){
                    // buffer = new byte[(int)(gap-index)];
                    // }
                    // int n = data.read(buffer);
                    //
                    // if(n != -1){
                    // index +=n;
                    // }
                    // else{
                    // break;
                    // }
                    // }
                    wis.skip(uploadedBytes - position);
                } catch (IOException e) {
                    throw PaasServerExceptionFactory.systemException(e.getMessage(), e);
                }
            }
        }
        if (revision == null) {
            revision = addRevision(file, fileSize, creatorId);
        }
        // init Handler
        MultiPartUploadHandleEntity handlerEntity = revisionBiz.getHandlerByRevision(revision.getId());
        if (handlerEntity == null) {
            handler = initChunk(file, revision);
        } else {
            handler = MultiPartUploadHandleEntity.getDto(handlerEntity);
        }

        if (uploadedBytes >= fileSize) {
            mergeChunk(revision, handler);
        } else {
            if (wis == null) {
                throw PaasServerExceptionFactory.validateException("file content is empty.", null);
            }
            PartEntity part = revisionBiz.getLatestPartByRevision(revision.getId());
            int partCount = revisionBiz.getPartsCountByRevision(revision.getId());
            while (true) {
                // upload chunk
                if (part == null || part.getIndex() < revision.getPartsCount()) {
                    part = addPart(revision, fileSize, partCount);
                    partCount++;
                    wis.setCount(0);
                    result = bulkStorage.uploadPart(handler, PartEntity.getDto(part), wis);
                    part.setKey(result.getKey());
                    part.setETag(result.getETag());
                    revisionBiz.insertPart(part);
                    LOGGER.info("write part :[ " + part.getIndex() + ", key :" + part.getKey() + ", size : "
                                + part.getSize() + " Bytes] success!");
                }
                // complete
                else {
                    mergeChunk(revision, handler);
                    break;
                }
            }
        }
        createDatabag(file, revision);
        if (folders.size() > 0) {
            return folders.get(0);
        }
        return file;
    }

    private InitResult initChunk(FileInfoEntity file, RevisionEntity revision) {
        InitResult handler = bulkStorage.initMultiPartUpload(StorageKeyNamingPolicy.getKeyForSeed(file.getId(),
                                                                                                  revision.getRevisionNumber()));
        MultiPartUploadHandleEntity handlerEntity = SimpleBeanConverter.converte(handler,
                                                                                 MultiPartUploadHandleEntity.class);
        handlerEntity.setRevisionId(revision.getId());
        // create new file
        if (fileBiz.getFile(file.getId()) == null) {
            fileBiz.insertFile(file);
        }
        revisionBiz.insertRevision(revision);
        revisionBiz.insertHandler(handlerEntity);
        LOGGER.info("init multiPart, key :" + handlerEntity.getKey());
        return handler;
    }

    private void mergeChunk(RevisionEntity revision, InitResult handler) {
        try {
            WriteResult result;
            List<PartEntity> parts = revisionBiz.getPartsByRevision(revision.getId());
            // 排序
            Collections.sort(parts, new Comparator<PartEntity>() {

                @Override
                public int compare(PartEntity o1, PartEntity o2) {
                    return o1.getIndex() - o2.getIndex();
                }
            });
            result = bulkStorage.completeMultiPartUpload(handler, PartEntity.getDtos(parts));
            revision.setStorageKey(result.getKey());
            revision.seteTag(result.getETag());
            revision.setPartsCount(0);
            revisionBiz.activeRevision(revision);
            LOGGER.info("complete multiPart, key :" + result.getKey());
        } catch (StorageException e) {
            throw PaasServerExceptionFactory.mergePartsException(e.getMessage(), e);
        }
    }

    private FileInfoEntity uploadCommon(String userId, String creator, String parentId, String path, long fileSize,
                                        InputStream data) {
        if (data == null) {
            throw PaasServerExceptionFactory.validateException("file content is empty.", null);
        }
        List<FileInfoEntity> folders = new ArrayList<FileInfoEntity>();
        FileInfoEntity file = initFile(userId, creator, parentId, path, fileSize, folders);
        RevisionEntity revision = revisionBiz.getUploadingRevision(file.getId());
        if (revision != null) {
            revisionBiz.deleteRevision(revision);
        }
        revision = addRevision(file, fileSize, creator);
        revision.setPartsCount(0);
        WriteResult result = bulkStorage.write(StorageKeyNamingPolicy.getKeyForSeed(file.getId(),
                                                                                    revision.getRevisionNumber()),
                                               data, fileSize);
        revision.setStorageKey(result.getKey());
        revision.seteTag(result.getETag());

        // new file
        if (fileBiz.getFile(file.getId()) == null) {
            fileBiz.insertFile(file);
        } else {
            fileBiz.updateUpdator(file.getId(), userId);
        }

        revisionBiz.activeRevision(revision);

        LOGGER.info("upload file content success, key :" + result.getKey());
        createDatabag(file, revision);
        if (folders.size() > 0) {
            return folders.get(0);
        }
        return file;
    }

    private void createDatabag(FileInfoEntity file, RevisionEntity revision) {
        // notify databag system to create, publish databag
        String publicToken = null;
        if (file.isPublished()) {
            PublicTokenEntity token = shareBiz.getPublishInfoByFile(file.getId());
            publicToken = token.getDatabagToken();
        }
        String dataBagId = webService.createDataBag(file, revision, publicToken);
        if (StringUtils.isNotBlank(dataBagId)) {
            revisionBiz.updateDataBagId(revision, dataBagId);
        }
    }

    private RevisionEntity addRevision(FileInfoEntity file, long fileSize, String creatorId) {
        RevisionEntity revision = new RevisionEntity();
        revision.setId(UUIDUtil.getUUIDStr());
        revision.setFileId(file.getId());
        revision.setTimestamp(DateUtil.getNow());
        revision.setFileSize(fileSize);
        revision.setCreator(creatorId);
        revision.setUpdator(creatorId);
        if (file.getActiveRevision() <= 0) {
            revision.setRevisionNumber(1);
        } else {
            revision.setRevisionNumber(file.getActiveRevision() + 1);
        }
        if (fileSize % BulkStorage.BLOCK_SIZE > 0) {
            revision.setPartsCount(((int) (fileSize / BulkStorage.BLOCK_SIZE)) + 1);
        } else {
            revision.setPartsCount((int) (fileSize / BulkStorage.BLOCK_SIZE));
        }

        return revision;
    }

    private PartEntity addPart(RevisionEntity revision, long fileSize, int partCount) {
        PartEntity part = new PartEntity();
        part.setId(UUIDUtil.getUUIDStr());
        part.setRevisionId(revision.getId());
        if (partCount <= 0) {
            part.setIndex(1);
        } else {
            part.setIndex(partCount + 1);
        }

        if (part.getIndex() * BulkStorage.BLOCK_SIZE > fileSize) {
            part.setSize(fileSize % BulkStorage.BLOCK_SIZE);
        } else {
            part.setSize(BulkStorage.BLOCK_SIZE);
        }

        return part;
    }

    @Override
    public long getUploadedBytes(String fileId) {
        RevisionEntity v = revisionBiz.getUploadingRevision(fileId);
        long size = 0;
        if (v != null) {
            List<PartEntity> parts = revisionBiz.getPartsByRevision(v.getId());
            if (parts != null) {
                for (PartEntity part : parts) {
                    size += part.getSize();
                }
            }
        }
        return size;
    }

    @Override
    public ReadResult downloadFile(String userId, String fileId, int revisionNum) {
        privilege.checkReadPrivilege(userId, fileId, true, true);
        FileInfoEntity doc = fileBiz.getNormalFile(fileId);
        if (doc.isFolder()) {
            throw PaasServerExceptionFactory.documentException("File[" + fileId
                                                                       + "] is a folder, please download it by package",
                                                               null).setErrorCode(ErrorCode.DOWNLOAD_PACKAGE);
        } else if (doc.getActiveRevision() <= 0) {
            throw PaasServerExceptionFactory.notFoundException("can not find available Revision to dowload.", null);
        }
        RevisionEntity downFile = getRevision(userId, doc, revisionNum);
        ReadResult response;
        response = bulkStorage.read(downFile.getStorageKey());
        response.setName(doc.getFullName());
        if (response.getLength() < 0) {
            response.setLength(downFile.getFileSize());
        }

        LOGGER.info("download file content, key:" + downFile.getStorageKey());
        return response;
    }

    @Override
    public ReadResult downloadFile(String userId, String fileId, int revisionNum, long start, long end) {
        privilege.checkReadPrivilege(userId, fileId, true, true);
        FileInfoEntity doc = fileBiz.getNormalFile(fileId);
        if (doc.isFolder()) {
            throw PaasServerExceptionFactory.documentException("File[" + fileId
                                                                       + "] is a folder, please download it by package",
                                                               null).setErrorCode(ErrorCode.DOWNLOAD_PACKAGE);
        }
        RevisionEntity downFile = getRevision(userId, doc, revisionNum);

        if (end < 0) {
            end = downFile.getFileSize() - 1;
        }
        ReadResult response;
        response = bulkStorage.read(downFile.getStorageKey(), start, end);
        response.setName(doc.getFullName());
        response.setRanges(new long[] { start, end, downFile.getFileSize() });
        LOGGER.info("download file content[Range], key:" + downFile.getStorageKey());

        return response;
    }

    private RevisionEntity getRevision(String userId, FileInfoEntity doc, int revisionNum) {
        if (revisionNum <= 0) {
            revisionNum = doc.getActiveRevision();
        }
        RevisionEntity v = revisionBiz.getRevision(doc.getId(), revisionNum);
        if (v == null) {
            throw PaasServerExceptionFactory.notFoundException(String.format("File cannot be found: id = %s, revision = %s",
                                                                             doc.getId(), revisionNum), null).setErrorCode(ErrorCode.FILE_NOT_EXIST);
        }
        return v;
    }

    @Override
    public void downloadFolder(String userId, String fileId, OutputStream out) {
        privilege.checkReadPrivilege(userId, fileId, true, true);
        try {
            ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(out));
            FileInfoEntity file = fileBiz.getNormalFile(fileId);
            int bufferSize = 40960;
            byte data[] = new byte[bufferSize];
            List<ReadResult> rrs;
            if (file.isFolder()) {
                rrs = this.packageFolder("", file);
                if (rrs.size() <= 0) {
                    throw PaasServerExceptionFactory.documentException("the folder[id:]"
                                                                               + fileId
                                                                               + "] is empty, can not download by package.",
                                                                       null).setErrorCode(ErrorCode.FOLDER_EMPTY);
                }
            } else {
                rrs = new ArrayList<ReadResult>();
                rrs.add(downloadFile(userId, file.getId(), -1));
            }

            int count;
            for (ReadResult rr : rrs) {
                if (StringUtils.isNotEmpty(rr.getName())) {
                    ZipEntry entry = new ZipEntry(rr.getName());
                    zos.putNextEntry(entry);
                    if (rr.getInputStream() != null) {
                        while ((count = rr.getInputStream().read(data, 0, bufferSize)) != -1) {
                            zos.write(data, 0, count);
                        }
                    }
                    LOGGER.info("Adding: " + rr.getName());
                }
            }
            zos.flush();
            zos.close();
        } catch (Exception e) {
            throw PaasServerExceptionFactory.documentException(e.getMessage(), e);
        }

    }

    private List<ReadResult> packageFolder(String path, FileInfoEntity folder) throws PaasServerException {
        List<ReadResult> iss = new ArrayList<ReadResult>();
        List<FileInfoEntity> files = fileBiz.getFiles(folder.getId(), null,
                                                      new Integer[] { DocumentConstant.FILE_STATUS_NORMAL }, null);
        if (files != null && files.size() > 0) {
            for (FileInfoEntity file : files) {
                if (file.isFolder()) {
                    iss.addAll(packageFolder(path + file.getName() + "/", file));
                } else {
                    String key = revisionBiz.getRevision(file.getId(), file.getActiveRevision()).getStorageKey();
                    ReadResult rs = bulkStorage.read(key);
                    rs.setName(path + file.getFullName());
                    iss.add(rs);
                }
            }
        } else {
            ReadResult folderResult = new ReadResult(null);
            folderResult.setName(path);
            iss.add(folderResult);
        }
        return iss;
    }

    @Override
    public String getFileId(String userId, String path) {
        return fileBiz.getFileId(userId, path, new Integer[] { DocumentConstant.FILE_STATUS_NORMAL });
    }

    @Override
    public void setBulkStorage(BulkStorage bulkStorage) {
        this.bulkStorage = bulkStorage;
    }

    @Override
    public Revision getUploadingRevision(String userId, String parentId, String name, long size) {
        privilege.checkReadPrivilege(userId, parentId, true, true);
        FileInfoEntity file = fileBiz.getFile(parentId, name, new Integer[] { FILE_STATUS_NORMAL },
                                              new Integer[] { FILE_TYPE_FILE });
        if (file != null) {
            RevisionEntity revision = revisionBiz.getUploadingRevision(file.getId());
            if (revision != null && revision.getFileSize().longValue() == size) {
                return RevisionEntity.getDto(revision);
            }
            return null;
        }
        return null;
    }

    @Override
    public int delExpiredFiles(String ownerId, int nDays, int limited) {
        List<FileInfoEntity> files = fileBiz.getExpiredFiles(ownerId, nDays, limited);
        for (FileInfoEntity file : files) {
            try {
                if (file.isFolder()) {
                    fileBiz.deleteExpiredFile(file);
                } else {
                    List<RevisionEntity> revisions = revisionBiz.getAllRevisions(file.getId());
                    for (RevisionEntity revision : revisions) {
                        revisionBiz.deleteRevision(revision);
                        webService.deleteDataBag(revision.getDataBagId(), null);
                    }
                    fileBiz.deleteExpiredFile(file);
                    WebService.notifySearch(FileInfoEntity.getDto(file, false), "delete");
                }
            } catch (StorageException e) {
                LOGGER.debug("delete expired file[" + file.getFullName() + "] fail.");
            }
        }
        return files.size();
    }

    @Override
    public void downloadFiles(String userId, String[] ids, OutputStream out) {
        for (String id : ids) {
            privilege.checkReadPrivilege(userId, id, true, true);
        }
        ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(out));
        int bufferSize = 40960;
        byte data[] = new byte[bufferSize];
        int count = 0;
        List<ReadResult> rrs = new ArrayList<ReadResult>();
        try {
            for (String id : ids) {
                FileInfoEntity file = fileBiz.getNormalFile(id);
                if (file.isFolder()) {
                    rrs.addAll(this.packageFolder(file.getName() + "/", file));
                } else {
                    rrs.add(downloadFile(userId, file.getId(), -1));
                }
            }

            for (ReadResult rr : rrs) {
                ZipEntry entry = new ZipEntry(rr.getName());
                zos.putNextEntry(entry);
                if (rr.getInputStream() != null) {
                    while ((count = rr.getInputStream().read(data, 0, bufferSize)) != -1) {
                        zos.write(data, 0, count);
                    }
                }
                LOGGER.info("Adding: " + rr.getName());
            }
            zos.flush();
            zos.close();
        } catch (Exception e) {
            throw PaasServerExceptionFactory.documentException(e.getMessage(), e);
        }
    }

    @Override
    public void createDataBag(String fileId) {
        FileInfoEntity file = fileBiz.getNormalFile(fileId);
        if (file != null && !file.isFolder()) {
            RevisionEntity revision = this.getRevision(file.getOwnerId(), file, file.getActiveRevision());
            createDatabag(file, revision);
        }
    }

//    @Override
//    public File removeRevision(String userId, String fileId, Integer revisionNum) {
//        if(fileId)
//        this.removeRevision(userId, revisionId)revisionBiz.getRevision(fileId, revisionNum);
//    }

    @Override
    public String getPath(String userId, String fileId, boolean encode) {
        List<DataTansitionObject> files = this.getPath(userId, fileId);
        StringBuilder path = new StringBuilder();
        for (DataTansitionObject file : files) {
            path.append(StringConst.SEPARATOR_SLASH);
            if(encode){
                path.append(HttpUtils.encodeUTF8(file.getName()));
            }
            else{
                path.append(file.getName());
            }
        }
        return path.toString();
    }
    
    @Override
    public List<DataTansitionObject> getPath(String userId, String fileId) {
        privilege.checkAnyPrivilege(userId, fileId, true, true);
        FileInfoEntity curFile = fileBiz.getNormalFile(fileId);
        List<FileInfoEntity> files = fileBiz.getParents(curFile);
        files.add(0, curFile);
        List<DataTansitionObject> ns = new ArrayList<DataTansitionObject>();
        for (FileInfoEntity file : files) {
            // 項目文件信息，获取路径时，需求检查路径上每个文件的权限, 此代码 可以进行优化
            if(StringUtils.isNotBlank(file.getProjectId())){
                Set<String> sPV = resourceBiz.getFilePrivileges(userId, file.getId(), true);
                if(sPV.size()==0){
                    throw PaasServerExceptionFactory.privilegeException("User["+userId+"] has no privilege to get File["+file.getId()+"]", null);
                }
            }
            DataTansitionObject n = new DataTansitionObject();
            n.setId(file.getId());
            n.setName(file.getFullName());
            ns.add(0, n);
        }
        return ns;
    }

    @Override
    public File moveFile(String userId, String fileId, String toParentId) {
        privilege.checkRemovePrivilege(userId, fileId, true, true);
        privilege.checkWritePrivilege(userId, toParentId, true, true);
        FileInfoEntity file = fileBiz.getNormalFile(fileId);
        if (!file.getParentId().equals(toParentId)) {
            if (!file.isFolder() && fileBiz.isDocExist(toParentId, file.getFullName())) {
                throw PaasServerExceptionFactory.existException("exist the same file [" + file.getFullName() + "]",
                                                                null).setErrorCode(ErrorCode.EXIST_SAME_FILE);
            } else if (file.isFolder() && fileBiz.isFolderExist(toParentId, file.getFullName())) {
                throw PaasServerExceptionFactory.existException("exist the same folder [" + file.getFullName() + "]",
                                                                null).setErrorCode(ErrorCode.EXIST_SAME_FOLDER);
            } else {
                fileBiz.updateParent(file, toParentId);
            }
        }
        File dto = FileInfoEntity.getDto(file, false);
        WebService.notifySearch(dto, "move");
        return dto;
    }

    // TODO 权限检查，添加 项目文件的UT之后，应该为：privilege.checkWritePrivilege(userId, fileId, true, false);
    @Override
    public void lockFile(String userId, String fileId) {
        privilege.checkWritePrivilege(userId, fileId, true, false);
        fileBiz.lockFile(userId, fileId);
    }

    //TODO 权限检查，添加 项目文件的UT之后，应该为：privilege.checkWritePrivilege(userId, fileId, true, false);
    @Override
    public void unlockFile(String userId, String fileId) {
        privilege.checkWritePrivilege(userId, fileId, true, false);
        FileInfoEntity file = fileBiz.getNormalFile(fileId);
        if (StringUtils.isNotBlank(file.getLockId())) {
            fileBiz.unlockFile(file.getLockId());
        }
    }
    /**
     * 将 给定的 权限格式，改成 继承格式
     * 
     * @param sPV
     * @return
     */
    private Set<String> selfPV2InheritPV(Set<String> sPV) {
        Set<String> iPV = new HashSet<String>();
        for (String pv : sPV) {
            // x 进入权限 不可继承
            if (DocumentConstant.FILE_ENTER.equals(pv.trim())) {
                continue;
            } else if (pv.trim().startsWith("i-")) {
                iPV.add(pv.trim());
            } else {
                iPV.add("i-" + pv.trim());
            }
        }
        return iPV;
    }

    /**
     * 获取 文件 到根目录的树结构
     **/
    @Override
    public File getTree(String userId, String fileId) {
        privilege.checkAnyPrivilege(userId, fileId, true, false);
        FileInfoEntity file = fileBiz.getFile(fileId);
        boolean projectFile = file.getProjectId() != null;
        List<File> parentFiles = FileInfoEntity.getDtos(fileBiz.getParents(file), false);
        if(parentFiles.size()>0){
            for(int i=parentFiles.size()-1; i>=0; i--){
                File curFile = parentFiles.get(i);
                //项目文件
                List<File> files;
                if(projectFile){
                    files = this.getProjectFiles(userId, curFile.getId(), null, null, false);
                }
                else{
                    files = FileInfoEntity.getDtos(fileBiz.getFiles(curFile.getId(), null, new Integer[] { DocumentConstant.FILE_STATUS_NORMAL }, null), false);
                }
                curFile.setFiles(files);
                
                // 组建 树结构
                if(i<parentFiles.size()-1){
                    List<File> siblingFiles = parentFiles.get(i+1).getFiles();
                    for (File f : siblingFiles) {
                        if (f.getId().equals(curFile.getId())) {
                            f.setFiles(curFile.getFiles());
                            break;
                        }
                    }
                }
            }
            return parentFiles.get(parentFiles.size() - 1);
        }
        else{
            return FileInfoEntity.getDto(file, false);
        }
    }

    @Override
    public File getProjectFolder(String userId, String projectId) {
        ProjectInfoEntity project =  projectBiz.getProjectById(projectId);
        if(project == null){
            throw PaasServerExceptionFactory.notFoundException("can not find Project["+projectId+"]", null);
        }
        privilege.checkAnyPrivilege(userId, project.getFolderId(), false, false);
        File folder = FileInfoEntity.getDto(fileBiz.getFileWithFolderCount(project.getFolderId()), false);
        resetPrivilege(userId, folder);
        folder.setDescription(project.getDescription());
        return folder;
    }

    @Override
    public File removeRevision(String userId, String revisionId) {
        RevisionEntity r = revisionBiz.getRevision(revisionId);
        if(r == null){
            throw PaasServerExceptionFactory.notFoundException("can not find Revision["+revisionId+"]", null);
        }
        privilege.checkRemovePrivilege(userId, r.getFileId(), true, false);
        FileInfoEntity file = fileBiz.getNormalFile(r.getFileId());
        if (!file.isFolder()) {
            RevisionEntity revision = revisionBiz.getRevision(revisionId);
            if (file.getCount() == 1) {
                this.remove(userId, r.getId());
            } else {
                revisionBiz.deleteRevision(revision);
                revisionBiz.refreshFile(file);
                if (StringUtils.isNotBlank(revision.getDataBagId())) {
                    // 如果文件是公开的，在通知数据包删除时，指定需要替换的数据包，以便公开
                    if(DocumentConstant.SHARE_TYPE_PUBLISHED.equals(file.getShareType()) || DocumentConstant.SHARE_TYPE_SHARED_AND_PUBLISHED.equals(file.getShareType())){
                        webService.deleteDataBag(revision.getDataBagId(), file.getDataBagId());
                    }
                    else{
                        webService.deleteDataBag(revision.getDataBagId(), null);
                    }
                }
            }
        }
        return FileInfoEntity.getDto(file, false);
    }
}
